#include "private/qtdbusnode_p.h"

#include <QMetaObject>
#include <QDBusConnection>
#include <QMetaProperty>

#include <private/dbus_minimal_p.h>

extern Q_DBUS_EXPORT QString qDBusGenerateMetaObjectXml(QString interface, const QMetaObject *mo, const QMetaObject *base, int flags);

QtDBusNode::QtDBusNode(const QString &name) : name{name}
{
}

QtDBusNode::~QtDBusNode()
{
}

QtDBusNode::Location QtDBusNode::registerObject(QObject *object, QStringList nodes, const QString &name)
{
    if (!nodes.empty()) {
        const QString &childName = nodes.takeFirst();
        QtDBusNode *childNode = child(childName);
        if (!childNode)
            childNode = addChild(childName);
        Location location = childNode->registerObject(object, nodes, name);
        if (!location.interface && childNode->empty())
            removeChild(name);
        location.nodes.prepend(this->name);
        return location;
    }
    return {{this->name}, addInterface(object, name)};
}

QObject *QtDBusNode::unregisterObject(QStringList nodes, const QString &name)
{
    if (!nodes.empty()) {
        const QString &childName = nodes.takeFirst();
        QtDBusNode *childNode = child(childName);
        if (!childNode)
            return nullptr;
        QObject *object = childNode->unregisterObject(nodes, name);
        if (childNode->empty())
            removeChild(childName);
        return object;
    }

    for (auto i = interfaceList.begin(); i != interfaceList.end(); ++i) {
        if (i->name != name)
            continue;
        QObject *object = i->object;
        interfaceList.erase(i);
        return object;
    }
    return nullptr;
}

QObject *QtDBusNode::objectAt(const QStringList &nodes, const QString &name)
{
    if (Interface *i = interface(nodes, name))
        return i->object;
    return nullptr;
}

QtDBusNode *QtDBusNode::child(const QString &name)
{
    for (QtDBusNode &child : children) {
        if (child.name == name)
            return &child;
    }
    return nullptr;
}

QtDBusNode *QtDBusNode::addChild(const QString &name)
{
    return &children.emplace_back(name);
}

QtDBusNode *QtDBusNode::child(QStringList nodes)
{
    if (nodes.empty())
        return this;
    if (QtDBusNode *childNode = child(nodes.takeFirst()))
        return childNode->child(nodes);
    return nullptr;
}

void QtDBusNode::removeChild(const QString &name)
{
    for (auto i = children.begin(); i != children.end(); ++i) {
        if (i->name != name)
            continue;
        children.erase(i);
        break;
    }
}

bool QtDBusNode::empty() const
{
    return children.empty() && interfaceList.empty();
}

bool QtDBusNode::exist(const QObject *object) const
{
    for (const QtDBusNode &child : children) {
        if (child.exist(object))
            return true;
    }

    for (const Interface &i : interfaceList) {
        if (i.object == object)
            return true;
    }
    return false;
}

QtDBusNode::LocationList QtDBusNode::interfaces(QStringList depth) const
{
    depth.append(name);

    LocationList result;
    for (const QtDBusNode &child : children)
        result.append(child.interfaces(depth));

    for (const Interface &i : interfaceList)
        result.append({depth, &i});

    return result;
}

QtDBusNode::LocationList QtDBusNode::locationsOf(const QObject *object, QStringList depth) const
{
    depth.append(name);

    LocationList result;
    for (const QtDBusNode &child : children)
        result.append(child.locationsOf(object, depth));

    for (const Interface &i : interfaceList) {
        if (i.object != object)
            continue;
        result.append({depth, &i});
    }
    return result;
}

QString QtDBusNode::introspect() const
{
    QString xml;
    for (const Interface &i : interfaceList)
        xml.append(i.introspection);

    for (const QtDBusNode &child : children) {
        if (child.interfaceList.empty())
            continue;
        xml.append(QStringLiteral("  <node name=\"%1\"/>\n").arg(child.name));
    }
    return xml;
}

QtDBusNode::Interface *QtDBusNode::interface(QStringList nodes, const QString &name)
{
    if (!nodes.empty()) {
        if (QtDBusNode *childNode = child(nodes.takeFirst()))
            return childNode->interface(nodes, name);
        return nullptr;
    }
    for (Interface &i : interfaceList) {
        if (i.name == name)
            return &i;
    }
    return nullptr;
}

QtDBusNode::Interface *QtDBusNode::addInterface(QObject *object, QString name)
{
    const QString &xml = qDBusGenerateMetaObjectXml(name, object->metaObject(), &QObject::staticMetaObject, QDBusConnection::ExportAllContents);
    if (xml.isEmpty())
        return nullptr;

    name = xml.section('"', 1, 1);

    for (auto i = interfaceList.cbegin(); i != interfaceList.cend(); ++i) {
        if (i->name == name)
            return nullptr;
    }

    interfaceList.push_back({object, name, xml});
    return &interfaceList.back();
}

bool QtDBusNode::removeInterface(const QString &name)
{
    for (auto i = interfaceList.begin(); i != interfaceList.end(); ++i) {
        if (i->name != name)
            continue;
        interfaceList.erase(i);
        return true;
    }
    return false;
}
